using System.Collections.Generic;
using Sirenix.OdinInspector;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
using UnityEngine;

namespace GPUAnimationLib.Runtime
{
    public class GPUAnimationManager : MonoBehaviour
    {
        [Button]
        void EnableKdTree(bool toEnable)
        {
            IsEnableKdTree = toEnable;
            mEntityKdTree.Enable = IsEnableKdTree;
            mEntityKdTree.UpdateFrameCount = KdTreeUpdateFrame;
        }

        [Title("是否启用对象KdTree")] public bool IsEnableKdTree;
        [Title("KdTree更新一次所需帧数")] public int KdTreeUpdateFrame;
        [Title("摄像机距离范围")] public Vector2 CameraRange;
        [Title("最远摄像机时动画更新帧数")] public int MaxAnimationUpdateFrame;
        [Title("动画更新频率曲线")] public AnimationCurve AnimationUpdateRateCurve;

        public static GPUAnimationManager Instance { get; private set; }
        static readonly int ShaderPropID_AnimationState = Shader.PropertyToID("_AnimationState");

        NativeParallelHashMap<int, AnimatorData> mAnimators;
        NativeList<AnimatorData> mAnimatorBuffer;
        Dictionary<int, AnimatorDataManaged> mManagedAnimators;
        Dictionary<int, UnsafeList<Matrix4x4>> mAnchorTransformsDict; //附加物计算数据（全局唯一）
        EntityKdTree mEntityKdTree;
        int mAnimatorId;

        int NewAnimatorId
        {
            get
            {
                mAnimatorId++;
                mAnimatorId %= int.MaxValue;
                return mAnimatorId;
            }
        }

        int mTargetAnimationUpdateFrame;
        int mCurrentAnimationUpdateFrame;

        bool mIsValid;

        public void Init()
        {
            if (mIsValid) return;
            mIsValid = true;
            mAnimatorId = 0;
            mAnimators = new NativeParallelHashMap<int, AnimatorData>(128, Allocator.Persistent);
            mAnimatorBuffer = new NativeList<AnimatorData>(128, Allocator.Persistent);
            mManagedAnimators = new Dictionary<int, AnimatorDataManaged>(128);
            mAnchorTransformsDict = new Dictionary<int, UnsafeList<Matrix4x4>>();
            mEntityKdTree = new EntityKdTree
            {
                Enable = IsEnableKdTree,
                UpdateFrameCount = KdTreeUpdateFrame
            };
            mTargetAnimationUpdateFrame = 1;
            mCurrentAnimationUpdateFrame = 0;
        }

        public void Dispose()
        {
            if (!mIsValid) return;
            mIsValid = false;
            ClearActions();
            if (mAnimators.IsCreated)
            {
                foreach (var item in mAnimators)
                    item.Value.Dispose();
                mAnimators.Dispose();
            }

            foreach (var item in mAnchorTransformsDict)
                item.Value.Dispose();
            mAnchorTransformsDict = null;
            if (mAnimatorBuffer.IsCreated)
                mAnimatorBuffer.Dispose();
            mManagedAnimators = null;
            mEntityKdTree.Dispose();
            mEntityKdTree = null;
        }

        void Awake()
        {
            Instance = this;
            Init();
        }

        void Update()
        {
            mEntityKdTree.Update1();
        }

        void LateUpdate()
        {
            if (mAnimators.Count() > 0)
            {
                UpdateActions();
                GPUAnimationUpdateJob job = new GPUAnimationUpdateJob()
                {
                    Animators = mAnimators,
                    AnimatorBuffer = mAnimatorBuffer,
                    DeltaTime = Time.deltaTime,
                };
                var handle = job.Schedule();

                mEntityKdTree.Update2();
                UpdateAnimationUpdateFrame();

                handle.Complete();

                mCurrentAnimationUpdateFrame++;
                if (mCurrentAnimationUpdateFrame >= mTargetAnimationUpdateFrame)
                {
                    mCurrentAnimationUpdateFrame = 0;
                    foreach (var item in mAnimators)
                    {
                        if (!mEntityKdTree.CheckInArea(item.Key)) continue;
                        var animator = item.Value;
                        if (animator.ProcessState == 0) continue; //关闭
                        if (animator.NeedUpdateToGPU)
                        {
                            UpdateToGPU(in animator);
                        }
                    }
                }
            }
        }

        void UpdateToGPU(in AnimatorData animator)
        {
            if (mManagedAnimators.TryGetValue(animator.AnimatorId, out var managed))
            {
                managed.MaterialPropertyBlock.SetMatrix(ShaderPropID_AnimationState, animator.AnimationState);
                managed.MeshRenderer.SetPropertyBlock(managed.MaterialPropertyBlock);

                //更新附加物
                if (managed.AttachmentTransforms != null && managed.AttachmentTransforms.Length > 0)
                {
                    foreach (var attachmentData in animator.AttachmentDatas)
                    {
                        var attachment = managed.AttachmentTransforms[attachmentData.AttachmentIndex];
                        attachment.localPosition = attachmentData.LocalPosition;
                        attachment.localRotation = attachmentData.LocalRotation;
                    }
                }
            }
        }

        void OnDestroy()
        {
            Dispose();
        }

        public int RegisterAnimator(GPUAnimationComponent component)
        {
            int animatorId = NewAnimatorId;
            var animator = new AnimatorData
            {
                AnimatorId = animatorId,
                ProcessState = 1,
                TotalFrames = component.TotalFrames,
            };
            InitAnchorTransforms(component, ref animator);
            mAnimators.Add(animatorId, animator);
            mManagedAnimators.Add(animatorId, new AnimatorDataManaged()
            {
                MeshRenderer = component.MainMeshRenderer,
                MaterialPropertyBlock = component.MaterialPropertyBlock,
                MaxAttachmentCount = component.AttachmentDatas?.Length ?? 0,
            });
            mEntityKdTree.Add(animatorId, component.MainMeshRenderer.transform);
            return animatorId;
        }

        public bool UnregisterAnimator(int animatorId)
        {
            if (!mAnimators.IsCreated) return false;
            if (mManagedAnimators.Remove(animatorId))
            {
                var animator = mAnimators[animatorId];
                animator.Dispose();
                mAnimators.Remove(animatorId);
                mEntityKdTree.Remove(animatorId);
                return true;
            }

            return false;
        }

        /// <summary>
        /// 添加附加物
        /// </summary>
        /// <param name="animatorId">动画状态机Id</param>
        /// <param name="attachmentTrans">附加物</param>
        /// <param name="attachmentIndex">附加物索引</param>
        public void AddAttachment(int animatorId, Transform attachmentTrans, int attachmentIndex)
        {
            if (attachmentIndex < 0) return;
            if (mManagedAnimators.TryGetValue(animatorId, out var managed))
            {
                if (managed.AttachmentTransforms == null)
                {
                    managed.AttachmentTransforms = new Transform[managed.MaxAttachmentCount];
                }

                managed.AttachmentTransforms[attachmentIndex] = attachmentTrans;
                mManagedAnimators[animatorId] = managed;

                var animator = mAnimators[animatorId];
                if (!animator.AttachmentDatas.IsCreated)
                    animator.AttachmentDatas =
                        new UnsafeList<AttachmentData>(managed.MaxAttachmentCount, Allocator.Persistent);
                animator.AttachmentDatas.Add(new AttachmentData()
                {
                    AttachmentIndex = attachmentIndex,
                });
                mAnimators[animatorId] = animator;
            }
        }

        /// <summary>
        /// 移除附加物
        /// </summary>
        /// <param name="animatorId">动画状态机Id</param>
        /// <param name="attachmentIndex">附加物索引</param>
        /// <returns></returns>
        public bool RemoveAttachment(int animatorId, int attachmentIndex)
        {
            if (attachmentIndex < 0) return false;
            if (mManagedAnimators.TryGetValue(animatorId, out var managed))
            {
                managed.AttachmentTransforms[attachmentIndex] = null;
                //释放内存
                {
                    bool isNoneAttachment = true;
                    for (int i = 0; i < managed.AttachmentTransforms.Length; i++)
                    {
                        if (managed.AttachmentTransforms[i] != null)
                        {
                            isNoneAttachment = false;
                            break;
                        }
                    }

                    if (isNoneAttachment)
                        managed.AttachmentTransforms = null;
                }
                mManagedAnimators[animatorId] = managed;

                var animator = mAnimators[animatorId];
                for (int i = 0; i < animator.AttachmentDatas.Length; i++)
                {
                    var attachmentData = animator.AttachmentDatas[i];
                    if (attachmentData.AttachmentIndex == attachmentIndex)
                    {
                        animator.AttachmentDatas.RemoveAt(i);
                        break;
                    }
                }

                mAnimators[animatorId] = animator;
                return true;
            }

            return false;
        }

        public bool TryGetAnimator(int animatorId, out AnimatorData animator)
        {
            return mAnimators.TryGetValue(animatorId, out animator);
        }

        public void SetAnimator(AnimatorData animator)
        {
            if (mAnimators.ContainsKey(animator.AnimatorId))
                mAnimators[animator.AnimatorId] = animator;
        }

        public bool TryGetAnimatorManaged(int animatorId, out AnimatorDataManaged animatorManaged)
        {
            return mManagedAnimators.TryGetValue(animatorId, out animatorManaged);
        }

        /// <summary>
        /// 播放动画
        /// </summary>
        /// <param name="animatorId">动画状态机Id</param>
        /// <param name="animationData">动画数据</param>
        /// <param name="speed">动画播放速度</param>
        /// <param name="startNormalizedTime">动画播放开始时间（标准化）</param>
        /// <param name="transitionTime">动画切换过度时间（单位：秒）</param>
        /// <param name="completed">动画结束事件</param>
        public void PlayAnimation(int animatorId, GPUAnimationData animationData, float speed = 1f,
            float startNormalizedTime = 0f, float transitionTime = 0.25f, CompletedAction completed = null)
        {
            if (TryGetAnimator(animatorId, out var animator))
            {
                animator.ProcessState = 1;
                if (animator.CurrentAnimationData.AnimationId != animationData.AnimationId) //播放不同动画
                {
                    animator.CurrentAnimationData = animationData;
                    animator.Speed = speed;
                    animator.StartNormalizedTime = startNormalizedTime;
                    animator.TransitionTime = transitionTime;
                    animator.State.CurrentStopped = false;
                    animator.State.PreviousStopped = false;
                }
                else //播放相同动画
                {
                    animator.Speed = speed;
                    animator.StartNormalizedTime = startNormalizedTime;
                    animator.TransitionTime = transitionTime;

                    animator.State.PreviousAnimationData = animator.State.CurrentAnimationData;
                    animator.State.CurrentAnimationData = animationData;
                    animator.State.BlendPreviousToCurrent = 0f;
                    animator.State.PreviousNormalizedTime = animator.State.CurrentNormalizedTime;
                    animator.State.CurrentNormalizedTime = animator.StartNormalizedTime;
                    animator.State.PreviousStopped = false;
                    animator.State.CurrentStopped = false;
                }

                mAnimators[animator.AnimatorId] = animator;
                if (completed != null)
                {
                    AddAction(completed, animationData.Length * 1f / animationData.SampleFrameRate);
                    //Debug.Log($"播放动画后添加完成事件后，完成事件总个数：{mActions.Count}");
                }
            }
        }

        /// <summary>
        /// 设置指定动画状态机的激活
        /// </summary>
        /// <param name="animatorId">动画状态机Id</param>
        /// <param name="activate">是否启用</param>
        public void SetAnimatorActive(int animatorId, bool activate)
        {
            if (!mIsValid) return;
            if (TryGetAnimator(animatorId, out var animator))
            {
                animator.ProcessState = activate ? 1 : 0;
                mAnimators[animator.AnimatorId] = animator;
            }
        }

        /// <summary>
        /// 获取角色KdTree
        /// </summary>
        public EntityKdTree GetCharacterKdTree()
        {
            return mEntityKdTree;
        }

        public bool TryGetCamera(out Camera cam)
        {
            cam = null;
            return false;
        }

        void UpdateAnimationUpdateFrame()
        {
            if (!TryGetCamera(out var cam))
            {
                mTargetAnimationUpdateFrame = 1;
            }
            else
            {
                var size = cam.orthographicSize;
                float ratio = Mathf.Clamp01((size - CameraRange.x) / (CameraRange.y - CameraRange.x));
                var value = Mathf.Clamp01(AnimationUpdateRateCurve.Evaluate(ratio));
                mTargetAnimationUpdateFrame = Mathf.RoundToInt(MaxAnimationUpdateFrame * value);
            }

            //Debug.Log($"AnimationUpdateFrame {mTargetAnimationUpdateFrame}");
        }

        void InitAnchorTransforms(GPUAnimationComponent component, ref AnimatorData animator)
        {
            var attachmentAnchorData = component.AttachmentAnchorData;
            if (attachmentAnchorData == null ||
                attachmentAnchorData.AnchorTransforms == null ||
                attachmentAnchorData.AnchorTransforms.Length == 0)
                return;
            int id = attachmentAnchorData.GetInstanceID();
            if (!mAnchorTransformsDict.TryGetValue(id, out var list))
            {
                var anchorTransforms = attachmentAnchorData.AnchorTransforms;
                list = new UnsafeList<Matrix4x4>(anchorTransforms.Length, Allocator.Persistent);
                for (int i = 0; i < anchorTransforms.Length; i++)
                    list.Add(anchorTransforms[i]);
                mAnchorTransformsDict.Add(id, list);
            }

            animator.AnchorTransforms = list;
        }

        public struct AnimatorDataManaged
        {
            public MeshRenderer MeshRenderer;
            public MaterialPropertyBlock MaterialPropertyBlock;
            public int MaxAttachmentCount;
            public Transform[] AttachmentTransforms;
        }

        List<CompletedAction> mActions = new List<CompletedAction>();

        void AddAction(CompletedAction action, float delay)
        {
            action.Delay = delay;
            mActions.Add(action);
        }

        void ClearActions()
        {
            mActions.Clear();
        }

        void UpdateActions()
        {
            if (mActions.Count > 0)
            {
                float deltaTime = Time.deltaTime;
                for (int i = 0; i < mActions.Count; i++)
                {
                    var action = mActions[i];
                    if (action.Valid == false)
                    {
                        mActions.RemoveAt(i);
                        i--;
                        continue;
                    }

                    action.Delay -= deltaTime;
                    if (action.Delay <= 0)
                    {
                        action.Execute();
                        mActions.RemoveAt(i);
                        i--;
                    }
                    else
                    {
                        mActions[i] = action;
                    }
                }
            }
        }
    }
}